package it.unica.informatica.enotes.manager;

import android.app.Activity;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Base64;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import it.unica.informatica.enotes.NotesActivity;
import it.unica.informatica.enotes.R;
import it.unica.informatica.enotes.notes.Note;
import it.unica.informatica.enotes.notes.AttachmentItem;

/**
 * This class perform all the operation related to the file manipulation for multimedia contents
 * @author Mirko Marras
 * @version 1.0
 */
public class MultimediaManager {

    private static final String[] ALLOWED_IMAGE_EXTENSIONS = {"jpg", "jpeg", "bmp", "dip", "tiff", "tif", "png", "gif"};
    private static final long ALLOWED_MAX_IMAGE_BYTE = 2048000;
    public static final String ALLOWED_MAX_IMAGE_MB = "max 2 MB";

    private static final String[] ALLOWED_VIDEO_EXTENSIONS = {"avi", "divx", "flv", "mp4", "mpeg", "ogv", "mpg", "wmv"};
    private static final long ALLOWED_MAX_VIDEO_BYTE = 8192000;
    public static final String ALLOWED_MAX_VIDEO_MB = "max 8 MB";

    private static final String[] ALLOWED_AUDIO_EXTENSIONS = {"m4a", "mp3", "ogg", "wav", "wma"};
    private static final long ALLOWED_MAX_AUDIO_BYTE = 2048000;
    public static final String ALLOWED_MAX_AUDIO_MB = "max 2 MB";

    private static final String NOTE_EXT = ".enotes";
    private static String TEMP_FILE_NAME = "";

    /**********************************************************************************************
     *         METHODS UTILITIES TO CHECK FILE REQUIREMENTS AND RETRIEVE FILE INFORMATION         *
     **********************************************************************************************/

    /**
     * Set the base directory where the activity will save the temporary files
     * @param activity
     */
    public static void setBaseDirectory(NotesActivity activity) {
        TEMP_FILE_NAME = activity.getExternalCacheDir() + File.separator;
    }

    /**
     * Check it the file stored at the passed uri respects the requirements based on its content type
     * @param type The content type stored in the file
     * @return True if the file respects the requirements, false otherwise
     */
    private static boolean checkExtensionRequirements(String filePath, String type) {
        /* Retrieve the file path from the uri and its file extension */
        String extension = filePath.substring(filePath.lastIndexOf(".") + 1);

        /* Returns true if the size and the extension of the file match the allowed params */
        if (type.equals(AttachmentItem.TYPE_AUDIO)) {
            return Arrays.asList(ALLOWED_AUDIO_EXTENSIONS).contains(extension);
        }

        if (type.equals(AttachmentItem.TYPE_IMAGE)) {
            return Arrays.asList(ALLOWED_IMAGE_EXTENSIONS).contains(extension);
        }

        if (type.equals(AttachmentItem.TYPE_VIDEO)) {
            return Arrays.asList(ALLOWED_VIDEO_EXTENSIONS).contains(extension);
        }

        return false;
    }

    /**
     * Get the absolute path of the file stored at the uri passed as parameter
     * @param uri The uri of the file to retrieve
     * @param activity The activity that manages the file
     * @return The absolute path of the file stored at the uri
     */
    public static String getPath(Uri uri, Activity activity) {
        Cursor cursor = activity.managedQuery(uri, null, null, null, null);
        cursor.moveToFirst();
        return cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA));
    }

    /**********************************************************************************************
     *           METHODS TO CREATE ATTACHMENTS FROM FILES CREATED/PICKED BY THE USER              *
     **********************************************************************************************/

    /**
     * Create a new attachment object based on the content of the temporary file created by the user
     * @param activity The activity that manages the file
     * @param tempName The name of the temporary file created by the user
     * @param type The type of the content stored into the file
     * @return A new attachment object that contains the data of the temporary file
     */
    public static AttachmentItem getAttachmentFromFile(Activity activity, String tempName, String type) {
        /* This AttachmentItem will store the decode of the attachment file */
        AttachmentItem result = null;
        Bitmap thumbnails = null;
        long maxFileSize = Long.MAX_VALUE;

        /* Retrieve the file created from an external application and that contains the desired attachment */
        File f = new File(Environment.getExternalStorageDirectory().toString());
        for (File temp : f.listFiles()) {
            if (temp.getName().equals(tempName)) {
                f = temp;
                break;
            }
        }

        /* Set the allowed max file size and the thumbnails of the attachment based on its type */
        switch (type) {
            case AttachmentItem.TYPE_AUDIO:
                maxFileSize = ALLOWED_MAX_AUDIO_BYTE;
                thumbnails = BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_action_audio);
                break;
            case AttachmentItem.TYPE_IMAGE:
                maxFileSize = ALLOWED_MAX_IMAGE_BYTE;
                thumbnails = MultimediaManager.decodeSampledBitmapFromFile(f, 50, 50);
                break;
            case AttachmentItem.TYPE_VIDEO:
                maxFileSize = ALLOWED_MAX_VIDEO_BYTE;
                thumbnails = ThumbnailUtils.createVideoThumbnail(f.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);
                break;
            default:
        }

        /* Verify if the file respect the allowed max file size */
        if (f.length() <= maxFileSize && checkExtensionRequirements(f.getName(),type)) {
            result = new AttachmentItem(MultimediaManager.decodeAttachmentFile(f), f.getName(), type, thumbnails);
        }
        else if (f.length() > maxFileSize) { /* If the attachment doesn't respect the allowed max file size */
            result = new AttachmentItem();
        }
        else if (!checkExtensionRequirements(f.getName(), type)) { /* If the attachment doesn't respect the allowed extensions */
            result = null;
        }

        /* Delete the temporary file used to store the attachment created with the external application */
        f.delete();

        return result;
    }

    /**
     * Create a new attachment based on the content of the file stored at the passed uri
     * @param activity The activity that manages the file
     * @param uri The uri of the file picked by the user
     * @param type The type of the content stored into the file at the passed uri
     * @return A new attachment object that contains the data of the file stored at the passed uri
     */
    public static AttachmentItem getAttachmentFromUri(Activity activity, Uri uri, String type) {
        /* This AttachmentItem will store the decode of the attachment file associated to the uri */
        AttachmentItem result = null;
        Bitmap thumbnails = null;
        long maxFileSize = Long.MAX_VALUE;

        /* Set the allowed max file size and the thumbnails of the attachment based on its type */
        switch (type) {
            case AttachmentItem.TYPE_AUDIO:
                maxFileSize = ALLOWED_MAX_AUDIO_BYTE;
                break;
            case AttachmentItem.TYPE_IMAGE:
                maxFileSize = ALLOWED_MAX_IMAGE_BYTE;
                break;
            case AttachmentItem.TYPE_VIDEO:
                maxFileSize = ALLOWED_MAX_VIDEO_BYTE;
                break;
            default:
        }

        String tempPath = getPath(uri, activity);
        File f = new File(tempPath);

        /* Check if the file respects the allowed size and an allowed extension */
        if (f.length() <= maxFileSize && checkExtensionRequirements(f.getName(), type)) {

            /* Create the thumbnails of the attachment based on the type of the attachment */
            switch (type) {
                case AttachmentItem.TYPE_AUDIO:
                    thumbnails = BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_action_audio);
                    break;
                case AttachmentItem.TYPE_IMAGE:
                    thumbnails = MultimediaManager.decodeSampledBitmapFromFile(f, 50, 50);
                    break;
                case AttachmentItem.TYPE_VIDEO:
                    thumbnails = ThumbnailUtils.createVideoThumbnail(f.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);
                    break;
                default:
            }

            /* Initialize a new attachment based on retrieved file */
            result = new AttachmentItem(MultimediaManager.decodeAttachmentFile(f), f.getName(), type, thumbnails);
        }
        else if (f.length() > maxFileSize) { /* If the attachment doesn't respect the allowed max file size */
            result = new AttachmentItem();
        }
        else if (!checkExtensionRequirements(f.getName(), type)) { /* If the attachment doesn't respect the allowed extensions */
            result = null;
        }

        return result;
    }

    /**
     * Get a BASE64 string that decode the content of the file representing the multimedia attachment
     * @param file The file that contains the multimedia attachment
     * @return A BASE64 string that decode the content of the multimedia attachment file
     */
    public static String decodeAttachmentFile(File file) {
        /* Create a byte buffer to store the content of the file of the attachment */
        FileInputStream fileInputStream;
        byte[] bFile = new byte[(int) file.length()];

        /* Read all the bytes of the file and store them */
        try {
            fileInputStream = new FileInputStream(file);
            fileInputStream.read(bFile);
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        /* Create the encode of the attachment bytes as a BASE64 string */
        return Base64.encodeToString(bFile, Base64.DEFAULT);
    }

    /**
     * Create a temporary file that stores the encode of the attachment content used to show it with an external application
     * @param attachment The attachment object to manage
     * @return A temporary file that stores the encode of the attachment content
     */
    public static File encodeAttachmentFile(AttachmentItem attachment) {
        /* Create a temporary file to store an attachment to show by an external application */
        File file = new File(TEMP_FILE_NAME + attachment.getName());

        /* Write te decode of the BASE64 attachment in the file */
        try {
            FileOutputStream os = new FileOutputStream(file, true);
            os.write(Base64.decode(attachment.getContent(), 0));
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return file;
    }

    /**
     * Calculate the in sample size for a bitmap image
     * @param options The options used to calculate the size
     * @param reqWidth The raw width for the bitmap image
     * @param reqHeight The raw height for the bitmap image
     * @return The in sample size for a bitmap image
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        /* Raw height and width of image */
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            /* Calculate the largest inSampleSize value that is a power of 2 and keeps both
               height and width larger than the requested height and width */
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

    /**
     * Decode a bitmap image from a file using efficiently store parameters
     * @param res The file that contains the image to decode
     * @param reqWidth The request width for the result image
     * @param reqHeight The request height for the result image
     * @return The bitmap object associated to the file passed as parameter
     */
    private static Bitmap decodeSampledBitmapFromFile(File res, int reqWidth, int reqHeight) {
        /* First decode with inJustDecodeBounds=true to check dimensions */
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(res.getAbsolutePath(), options);

        /* Calculate inSampleSize */
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        /* Decode bitmap with inSampleSize set */
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(res.getAbsolutePath(), options);
    }

    /**********************************************************************************************
     *                   METHODS TO MANAGE THE FILE TO SEND/RECEIVE BY MAIL                      *
     **********************************************************************************************/
    /**
     * Create a temporary file that stores the content of the note as JSON
     * @param note The note to send
     * @return A temporary file that contains the content of the note as JSON
     */
    public static File createFileForMail(Note note) {
        /* Create the name of the file based on the note title */
        String filename = note.getTitle().replace(' ', '_');

        /* Create the associated file */
        File file = new File(TEMP_FILE_NAME + filename + NOTE_EXT);
        FileWriter fileWriter = null;

        /* Write the JSON code of the note to the file */
        try {
            fileWriter = new FileWriter(file);
            fileWriter.write(JsonManager.toJSON(note));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileWriter.flush();
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return file;
    }

    /**
     * Create a note object based on the content of the file at the file path
     * @param filePath The file path of the file that contains the content of the note as JSON
     * @return The note object that contains the data associated to the JSON string into the file
     */
    public static Note readFileFromMail(String filePath) {
        /* This Note will store the note associated to the the file with the file path parameter */
        Note note = new Note();
        File file = new File(filePath);

        /* Create a JSONObject that store the JSON string contained in the file */
        JSONObject json = null;
        JSONParser parser = new JSONParser();
        try {
            FileReader fileReader = new FileReader(file);
            json = (JSONObject) parser.parse(fileReader);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        /* Set the properties of the note to the retrieved values */
        note.setTitle(file.getName().replace(NOTE_EXT, "").replace('_', ' '));
        note.setContent(JsonManager.getNoteContent(json.toJSONString()));
        note.setAttachments(JsonManager.getNoteAttachments(json.toJSONString()));

        return note;
    }

}
